Skip to content

[feat] canvas: copy pasted media between canvases on the server - #82

Merged
princepride merged 1 commit into
mainfrom
feat/server-side-asset-copy
Nov 4, 2026
Merged

princepride merged 1 commit into
mainfrom
feat/server-side-asset-copy

Conversation

@princepride

Copy link
Copy Markdown
Collaborator

Summary

#80 修好了跨画布粘贴,但素材仍经过浏览器中转:浏览器先把复制的文件上传到目标工程,登记后为了显示预览再下载一遍。通过远程端口转发访问时,一张 4 MB 的图片要几秒才能粘贴完成。本 PR 改为在服务器上复制素材,粘贴过程中浏览器不收发任何素材字节。

新接口。 PUT /api/creation/projects/{id}/uploads/{handle}/copy,请求体为 {projectId, assetId, fingerprint?}。

  • 服务器读取来源工程中已校验的素材,将字节写入目标工程的上传暂存区,返回与普通上传相同的 UploadInfo。
  • 之后仍通过现有的 asset.import 登记。复制结果就是目标工程里的一次普通上传,来源记为 upload,复用同一套文件探测、大小限制、同句柄重放和冲突规则,不新增命令或契约。
  • 读取来源工程不申请编辑会话;目标工程须在当前会话中打开,要求与浏览器上传相同。
  • 请求带 fingerprint 时,若来源素材内容已变化,返回 409,不复制新内容。

粘贴流程。

  • 复制时为每个媒体字段记下来源素材的 ID 和指纹。素材 ID 优先取节点记录的值;刚上传、尚未记录的节点,按预览地址向来源工程客户端反查。
  • 粘贴时,已保存的素材走服务器复制。尚未保存的素材,或复制失败(来源已变化或已删除),回退为上传复制时读到的字节。
  • 预览直接使用复制时已在页面中的字节,不再从服务器下载刚登记的素材。

Test Plan / Result

本地工作树:Python 3.12、Node.js 24.22.1,Python 命令使用 PYTHONPATH=packages/core/src:apps/api/src。浏览器使用隔离 API/Vite 和 Chromium,启动方式见 docs/development/validation.md。

检查 命令 结果
复制接口 python -m pytest tests/test_creation_assets.py -q 35 passed。新增覆盖:复制后登记、内容与指纹一致、来源工程不变、同句柄重试返回同一结果;目标未打开时 409,来源不存在时 404,指纹不符时 409,同句柄换来源时 409,多余字段时 422
迁移逻辑 node --test tests/clipboardMedia.test.ts 5 passed。新增:按素材 ID 或预览地址找到来源,未知素材不走服务器复制
跨画布粘贴 node tests/browser/cross_canvas_paste.mjs(已在 CI 中) 通过。粘贴期间只有两次 PUT …/uploads/<id>/copy,没有素材上传或 /content 下载;保存、再次粘贴复用、重新打开后仍可读取
前端 npm test 524 passed
构建 / 契约 npm run build、python scripts/generate_contracts.py --check 通过
Python python -m pytest tests -q 854 passed,2 failed。失败的 test_runtime_readiness::test_runtime_catalog_can_skip_probes_and_check_one_harness 与 test_tool_lifecycle::test_another_live_worker_is_not_marked_interrupted 在本环境的 main(5581f19f)上同样失败

耗时。 在 Chromium 中用 CDP 将网络限制为上下行各 16 Mbit/s、延迟 40 ms,模拟远程端口转发,粘贴一张刚上传的 4 MB PNG,测量从粘贴到图片显示的时间:

版本 图片显示 主要请求
main 4.7 s 上传 4 MB 用时 2.2 s,登记后下载 4 MB 用时 2.2 s
本 PR 0.43 s 复制请求 0.17 s,登记 0.09 s

Known issues / Validation limits

  • 资产库的"从其他画布复制素材"(copyCanvasSource)仍经浏览器中转,可在后续改用此接口。
  • 耗时数据来自本机模拟限速,不是真实远程连接的测量。
  • 回退上传路径(来源已变化或已删除)只有单元测试和接口层的 409 测试覆盖,没有浏览器端到端测试。

🤖 Generated with Claude Code

A cross-canvas paste uploaded each copied file from the page and then
downloaded it again for the preview, so every image crossed the browser's
connection twice. Over a remote port forward a 4 MB image took seconds.

PUT /api/creation/projects/{id}/uploads/{handle}/copy now stages another
project's verified asset as an upload in the target project, without its
bytes leaving the server. It takes the expected fingerprint and refuses a
source that changed. Registration still goes through asset.import, so the
copy is an ordinary upload with the same file checks and replay rules.

A paste copies saved media this way, finding the source asset by its
recorded ID or, for a node that has just been imported, by its preview
URL. Unsaved media, or a source that changed or is gone, is uploaded from
the bytes captured at copy. The preview reuses those bytes instead of
reading the new asset back.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: princepride <wangzhipeng628@gmail.com>
@princepride
princepride merged commit 3b34750 into main Nov 4, 2026
@hsliuustc0106
hsliuustc0106 deleted the feat/server-side-asset-copy branch November 6, 2026 08:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant